/* banssh.cc --
 * Copyright 2009  cwxstat LLC., Elkins Park, Pennsylvania.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors:
 *     Mike Chirico <mchirico@gmail.com>
 *     
 */




/*
This is the Cpp verison



There is still an issue with the headers. I can't use
Pthread_mutex_lock, Pthread_detach ... etc. All the function call's
with caps, because of the way the libraries are included in engine.c

*/


#include <unistd.h>
#include <errno.h>

#include <pthread.h>



#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libaudit.h>
#include <auparse.h>
#include <syslog.h>
#include "common.h"
#include "conf.h"
#include "ipumap.h"

#include "thread_wrapper.h"


#define MAXLOGINS 3
#define MAXLINE 1024

extern int errno;
static void cleanExec(char *s);


IPUmap *ipm;


pthread_mutex_t log_mutex;
struct L_Buf
{
	char *s;
	char *f;
};






int
logprocinfo(char *s, char *file)
{
	int fp, error;

	if ((fp = open(file, O_RDWR | O_CREAT, 0600)) == -1) {
		syslog(LOG_ERR, "Can't open data: %s\n", strerror(errno));
		fprintf(stderr, "Can't open data: %s\n", strerror(errno));
		return -1;
	}

	write(fp, s, strlen(s));

	while (((error = close(fp)) == -1) && (errno == EINTR));
	if (error == -1)
		return -1;

	return 0;

}

static void *
asyc_log(void *arg)
{
	char *s;
	char *f;

	s = ((struct L_Buf *)arg)->s;
	f = ((struct L_Buf *)arg)->f;

	Pthread_mutex_lock(&log_mutex);
	logprocinfo(s, f);
	Pthread_mutex_unlock(&log_mutex);
	free(s);
	free(f);
	free(arg);
	Pthread_detach(pthread_self());
	return (NULL);
}

static void *
Asyc_Log(int tmpFLG, int problems, int sleep, const char *file)
{

	static unsigned long count = 0;
	pid_t pid;
	struct L_Buf *l_b;
	pthread_t t;

	pid = getpid();

	l_b = (struct L_Buf *)malloc(sizeof(struct L_Buf));
	if (l_b == NULL) {
		syslog(LOG_ERR, "l_b is NULL");
		return NULL;
	}
	l_b->s = (char *)malloc(sizeof(char) * 1024);
	if (l_b->s == NULL) {
		syslog(LOG_ERR, "l_b->s is NULL");
		return NULL;
	}
	l_b->f = (char *)malloc(sizeof(char) * 1024);
	if (l_b->f == NULL) {
		syslog(LOG_ERR, "l_b->f is NULL");
		return NULL;
	}
	strncpy(l_b->f, file, 1023);

	snprintf(l_b->s, 1024,
		 "pid=%d tmpFLG=%d problems=%d sleep=%d count=%ld\n", pid,
		 tmpFLG, problems, sleep, ++count);

	Pthread_create(&t, NULL, &asyc_log, l_b);

	return NULL;
}







FILE *
Popen(const char *command, const char *mode)
{
    FILE *fp;

    if ((fp = popen(command, mode)) == NULL)

        syslog(LOG_ERR, "popen error");
    return (fp);
}

int
Pclose(FILE * fp)
{
    int n;

    if ((n = pclose(fp)) == -1)

        syslog(LOG_ERR, "pclose error");
    return (n);
}

char *
Fgets(char *ptr, int n, FILE * stream)
{
    char *rptr;

    if ((rptr = fgets(ptr, n, stream)) == NULL && ferror(stream))

        syslog(LOG_ERR, "fgets error");

    return (rptr);
}

void
Fputs(const char *ptr, FILE * stream)
{
    if (fputs(ptr, stream) == EOF)

        syslog(LOG_ERR, "fputs error");
}

SSL_CTX *
setup_client_ctx(void)
{
    SSL_CTX *ctx;

    ctx = SSL_CTX_new(SSLv23_method());
    if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
        int_error("Error loading CA file and/or directory");
    if (SSL_CTX_set_default_verify_paths(ctx) != 1)
        int_error("Error loading default CA file and/or directory");
    if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
        int_error("Error loading certificate from file");
    if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
        int_error("Error loading private key from file");
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
    SSL_CTX_set_verify_depth(ctx, 4);
    SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
    if (SSL_CTX_set_cipher_list(ctx, CIPHER_LIST) != 1)
        int_error("Error setting cipher list (no valid ciphers)");
    return ctx;
}

static int
check_addr(char *s)
{
    int cnt = 0;

    while (*s != '\0' && cnt < 15) {
        if (*s >= '0' && *s <= '9') {
            s++;
        }
        else {
            if (*s == '.' || *s == ' ' || *s == '\n') {
                s++;
            }
            else {
                return -1;
            }
        }
        cnt++;
    }
    if (cnt < 7)
        return -1;
    return cnt;
}

/* Quick and dirty. This needs to be fixed
   but we need some way to protect the possibility of sending
   bogus commands to the shell.

*/
static void
cleanExec(char *s)
{
    int goodc = 0, badc = 0;

    if (s == NULL)
        return;
    while (*s != '\0') {
        if ((*s >= 65 && *s <= 90) || (*s >= 97 && *s <= 122)
                || *s == ' ' || *s == '.' || *s == '/' || (*s >= '0'
                        && *s <=
                        '9')) {
            goodc++;
        }
        else {
            *s = '_';
            badc++;
        }
        /* Some shells limited by 128 characters? */
        if ((goodc + badc) >= 128)
            *s = '\0';
        else
            s++;
    }

}




static int
act_on_event(auparse_state_t * au,int succ_flg,char *addr, char *acct)
{
    FILE *fp;
    char buf[MAXLINE];
    char s_exec[MAXLINE];


    // failed && login type && tried a few times
    if ( succ_flg < 0 &&  auparse_get_type(au) == 1112 && ipm->check(addr,acct)  ) {
        if (check_addr(addr) < 0) {
#ifdef DEBUG_SYSL
            syslog(LOG_ERR,
                   "addr is zero. I cannot block and someone is attacking");
#endif
            snprintf(s_exec, MAXLINE,
                     EXEPROG);

        }
        else {
            snprintf(s_exec, MAXLINE,
                     "%s %s %s", EXEPROG,
                     addr, acct);
            cleanExec(s_exec);
        }
        fp = Popen(s_exec, "r");
        while (Fgets(buf, MAXLINE, fp) !=
                NULL) {
#ifdef DEBUG_SYSL
            syslog(LOG_ERR, "%s", buf);
#endif
        }
        Pclose(fp);

        printf("\n succ_flg(%d),acct=(%s),addr=(%s)\n", succ_flg, acct, addr);
        succ_flg = 0;
        addr[0] = '\0';
    }

    return succ_flg;
}







/*
  Make this much simpler. That's critical for getting something
  that not only work, but can be modified.
*/
static void
auparse_callback(auparse_state_t * au,
                 auparse_cb_event_t cb_event_type, void *user_data)
{
    int *event_cnt = (int *)user_data;
    int record_cnt;
    static int succ_flg = 0;
    char acct[MAXLINE];
    char addr[MAXLINE];



			Asyc_Log(0, 0, 0,
				 "/var/log/banssh/lock/bansshcpp");




    if (cb_event_type == AUPARSE_CB_EVENT_READY) {
        if (auparse_first_record(au) <= 0) {

            syslog(LOG_ERR, "can't get first record\n");
            return;
        }
        /*
           syslog(LOG_ERR,"succ_flg(%d) event %d has %d records\n",succ_flg, *event_cnt,
           auparse_get_num_records(au)); */

        /*
                    Slow down on this, since there is still work to be done
                    on this. There is a login and then an accept credentials
                    step. It appears you'll need both.
         */
        succ_flg = 0;
        do {
            // LOGIN or USER_ACCT

            if (auparse_get_type(au) == 1112 || auparse_get_type(au) == 1101) {
                acct[0] = '\0';
                addr[0] = '\0';

                printf("succ_flg(%d) record %d of type %d(%s) has %d fields\n", succ_flg, record_cnt, auparse_get_type(au), audit_msg_type_to_name(auparse_get_type(au)), auparse_get_num_fields(au));

#ifdef DEBUG_SYSL
                syslog(LOG_ERR,
                       "    line=%d file=%s\n",
                       auparse_get_line_number(au),
                       auparse_get_filename(au) ?
                       auparse_get_filename(au) : "None");
#endif

                const au_event_t *e =
                    auparse_get_timestamp(au);
                if (e == NULL) {
                    return;
                }
                printf("    event time: %u.%u:%lu, host=%s\n",
                       (unsigned)e->sec, e->milli,
                       e->serial, e->host ? e->host : "?");
                auparse_first_field(au);
                do {
#ifdef DEBUG_SYSL
                    syslog(LOG_ERR,
                           "        %s=%s (%s)\n",
                           auparse_get_field_name
                           (au),
                           auparse_get_field_str
                           (au),
                           auparse_interpret_field(au));
#endif

                    if (strncmp
                            (auparse_get_field_name(au),
                             "addr", 4) == 0) {
                        snprintf(addr, MAXLINE,
                                 "%s",
                                 auparse_interpret_field
                                 (au));
#ifdef DEBUG_SYSL
                        syslog(LOG_ERR, "YES1\n");
#endif
                    }
                    if (strncmp
                            (auparse_get_field_name(au),
                             "acct", 4) == 0) {
                        snprintf(acct, MAXLINE,
                                 "%s",
                                 auparse_interpret_field
                                 (au));
#ifdef DEBUG_SYSL
                        syslog(LOG_ERR, "YES2\n");
#endif
                    }

                    if (strncmp
                            (auparse_get_field_name(au),
                             "res", 3) == 0) {
                        if (strncmp
                                (auparse_interpret_field
                                 (au), "failed",
                                 6) == 0) {

                            succ_flg=-1;

                        } else {
                            succ_flg=1;

                            if (auparse_get_type(au) == 1101){

                                ipm->succlogin(addr,acct);
                                fprintf(stderr,"ipm->succlogin(%s,%s);\n",addr,acct);
                            }
                        }
                    }

                } while (auparse_next_field(au) > 0);

                // Total logins for everything
                fprintf(stderr,"ALL: succ_flg=%d addr(%s) acct(%s) auparse_get_type(au)=%d\n",succ_flg,addr,acct,auparse_get_type(au));
                succ_flg=act_on_event(au,succ_flg,addr,acct);


                record_cnt++;

            }

        } while (auparse_next_record(au) > 0);
        (*event_cnt)++;
        // Remember, you'll all events going through this.

    }
}

int
do_client_loop(SSL * ssl)
{
    int err, nread, len;
    char buf[1024];
    buf[0] = '\0';

    auparse_state_t *au;
    int event_cnt = 1;
    au = auparse_init(AUSOURCE_FEED, 0);
    auparse_add_callback(au, auparse_callback, &event_cnt, NULL);

    do {

        for (nread = 0; nread < (int)sizeof(buf); nread += err) {
            err = SSL_read(ssl, buf + nread, sizeof(buf) - nread);
            if (err >= 0) {
                buf[nread + err] = '\0';
                len = strlen(buf + nread);
                auparse_feed(au, buf + nread, len);
            }
            if (err <= 0)
                break;
        }

        if (event_cnt >= 1000) {

            auparse_flush_feed(au);
            auparse_destroy(au);
            event_cnt = 1;
            au = auparse_init(AUSOURCE_FEED, 0);
            auparse_add_callback(au, auparse_callback,
                                 &event_cnt, NULL);

        }

    } while (err > 0);

    auparse_flush_feed(au);
    auparse_destroy(au);

    return 1;
}

int
main_ssl(int argc, char **argv)
{
    BIO *conn;
    SSL *ssl;
    SSL_CTX *ctx;
    long err;

    init_conf();
    init_OpenSSL();
    seed_prng();
    ipm = new IPUmap(USER_WHITE_LIST);
    fprintf(stderr,"%s\n",USER_WHITE_LIST);


    ctx = setup_client_ctx();

    syslog(LOG_ERR, "SERVER= %s:%s\n", SERVER, PORT);
    conn = BIO_new_connect(SERVER_PORT);
    if (!conn)
        int_error("Error creating connection BIO");

    if (BIO_do_connect(conn) <= 0)
        int_error("Error connecting to remote machine");

    ssl = SSL_new(ctx);
    SSL_set_bio(ssl, conn, conn);
    if (SSL_connect(ssl) <= 0)
        int_error("Error connecting SSL object");
    if ((err = post_connection_check(ssl, SERVER)) != X509_V_OK) {

        syslog(LOG_ERR, "-Error: peer certificate: %s\n",
               X509_verify_cert_error_string(err));
        int_error("Error checking SSL object after connection");
    }

    syslog(LOG_ERR, "SSL Connection opened\n");
    if (do_client_loop(ssl))
        SSL_shutdown(ssl);
    else
        SSL_clear(ssl);

    syslog(LOG_ERR, "SSL Connection closed\n");

    SSL_free(ssl);
    SSL_CTX_free(ctx);
    delete(ipm);
    free_conf();
    return 0;
}

int
main(int argc, char **argv)
{
    int i = 0;

    daemon(0, 0);

    syslog(LOG_ERR, "banssh (cpp version) loaded");
    /* This for loop is is the connection gets dropped.
       We're the client, and we need to try and connect to the server */
    while (1) {
        for (i = 0; i < 30; ++i) {
            main_ssl(argc, argv);
            sleep(2 * i);

            syslog(LOG_ERR,
                   "clientparse trying to connect: (%d) seconds.",
                   2 * i);
        }
    }

    return 0;
}
